#ifndef MATRIX_HH
#define MATRIX_HH
#include "codedblock.hh"

CLICK_DECLS


CLICK_SIZE_PACKED_STRUCTURE(struct more_fge {,
    uint16_t _k;
    uint16_t _size; //size of nack packet which follows
});


class Matrix {
    friend class MatrixMgr;
public:
    /* The rules of the Matrix are:
    1) blocks in the matrix need to be of common k and datasize
    -- both can be set at initialization, datasize can be set at first add()
    2) ~Matrix does not destroy the stored blocks, this allows blocks to be shared
    3) clear() will destroy the stored blocks, be cautious when you call it
    4) to add blocks to the Matrix always use add() they preserve the invariants
    */
    Matrix(int k, int dsize = -1) : _k(0), _rank(0), _blocks(0), _datasize(dsize) { init(k); }

    // NOTE: you need to clear() explicitly
    virtual ~Matrix(){ delete[] _blocks; _blocks = 0; }
    Matrix(const Matrix &m) : _k(m._k), _rank(m._rank), _datasize(m._datasize) {
        _blocks = new CodedBlock*[_k];
        for(int i = 0; i < _k; ++i) {
            _blocks[i] = m._blocks[i];
        }
    }

    // free the stored blocks
    void clear();

    String print() const;

    const CodedBlock *operator[](int i) const { return _blocks[i]; }

    int k() const { return _k; }
    int rank() const { return _rank; }
    int datasize() const {return _datasize; }
    bool empty() const { return _rank == 0; }
    bool full() const { return _rank == _k; }
protected:
    // make empty room for k blocks
    void init(int k); // will not clear()

    void update_datasize(int size) {
        if(_datasize < 0)
            _datasize = size;

        massert(size == _datasize, "%d == %d", size, _datasize);
    }
    CodedBlock *&operator[](int i) { return _blocks[i]; }

    CodedBlock *remove(int i) {
        CodedBlock *t = _blocks[i]; assert(t);
        _blocks[i] = 0; --_rank;
        return t;
    }
    void add(int i, CodedBlock *cb) {
        /*click_chatter("ADD %d [%s]\n", i, cb->print().c_str());
        if(_blocks[i]){
            click_chatter("Matrix\n %s adding at %d\n %s\n", print().c_str(), i, cb->print().c_str());
        }
        */
        assert(!_blocks[i]);
        _blocks[i] = cb; ++_rank;
    }

    int _k;
    int _rank;
    CodedBlock **_blocks;
    int _datasize; // of stored blocks
};


class PartialGEMatrix: public Matrix {
protected:
    bool partial(CodedBlock *block) const;
public:
    PartialGEMatrix(int k, int ds = -1) : Matrix(k, ds) {}
    PartialGEMatrix(const PartialGEMatrix &m) : Matrix(m) {}

    bool add(CodedBlock *block);

    // check if the block would increase the rank
    bool contains(const CodedBlock *block) const {
        CodedBlock cb(*block);
        return !partial(&cb);
    }
};

class FullGEMatrix: public PartialGEMatrix {
public:
    FullGEMatrix(int k, int ds) : PartialGEMatrix(k ,ds) {}
    FullGEMatrix(const FullGEMatrix &m) : PartialGEMatrix(m) {}


    bool add(CodedBlock *block);
};


CLICK_ENDDECLS

#endif //MATRIX_HH

